. ($(Get-ScriptDir) + "\DBFrameworkConfig.ps1" )

#region $PatchMatchClass
# -------------------------------------------------------------------------------------------
$PatchMatchClass = New-PSClass PatchMatch {
	note Manifest
	note Matches
	note PatchGroup
	note FileHandler
	
	note -private DatabaseDir 
	note -private FullFileName
	
	property FullFileName {
		$private.fullFilename
	}

	property FileName {
		$private.FullFileName.name
	}
	
	Constructor {
		$this.Manifest = $Args[0]
		$this.FileHandler = $Args[1]
		[system.IO.DirectoryInfo] $private.DatabaseDir = $Args[2]
		$this.PatchGroup = $Args[3]
		
		[system.io.FileInfo] $private.fullFilename = [system.io.path]::combine($private.DatabaseDir.Fullname,$this.Manifest.path)

		[void] ($private.fullFilename.Name -match $this.PatchGroup.Pattern)
		# $Matches gets set by -match
		$this.Matches = $Matches
	}
	
	method ReplacePatternValues {
		$text = $Args[0]
		
		foreach ($key in $this.Matches.Keys)
		{
			if ($key -match "^[a-z|A-Z]\w*$")
			{
				$source = '$(' + $key + ')'
				$text = $text.Replace($source, $this.Matches[$key])
			}
		}
		$text
	}
	
	method ExecuteValidators { 
		$content = $Args[0]
		if ($this.FileHandler.Validators -ne $null)
		{
			$errorFound = $FALSE
			foreach ($validator in $this.FileHandler.Validators.Validator)
			{
				switch ($validator.Type) {
					'RegEx-Fail' {
						$ValidatorMatches = $content -match $validator.pattern
						if ($ValidatorMatches)
						{
							Log-Error $validator.message
							$errorFound = $TRUE
						}
					}
					default {
						Throw "Unknown Validator Type '$($validator.Type)'"
					}
				}
			}
			if ($errorFound)
			{
				Throw "Validators Failed"
			}
		}
	}
	
	method ExecuteHandler {
		$HandlerName = $Args[0]
		$DBDeployProvider = $Args[1]
		
		$Handler = $this.FileHandler.GetHandler($HandlerName)
		
		if ($Handler)
		{
			[array] $ExcuteNodes = @([psobject] $Handler.Execute)
			
			foreach ($ExcuteNode in $ExcuteNodes)
			{
				if ($ExcuteNode.Type)
				{
					$DBDeployProvider.ExecuteScript($ExcuteNode.Type, $this.ReplacePatternValues($ExcuteNode))
				}
				elseif ($ExcuteNode.Script)
				{
					$script = $this.PatchGroup.DBFrameworkConfig.Scripts.GetScript($ExcuteNode.Script)
					
					if ($script -eq $null)
					{
						Throw "Unknown Execute Script '$($ExcuteNode.Script)'"
					}
					Log-Message "Execute Script $($script.Name)" -Level Verbose
					$DBDeployProvider.ExecuteScript($script.Type, $this.ReplacePatternValues($script))
				}
			}
		}
	}
}
#endregion

#region $PatchMatchCollectionClass
# -------------------------------------------------------------------------------------------
$PatchMatchCollectionClass = New-PSClass PatchMatchCollection {
	note HandlerExecution
	note PatchMatches @()
	note DBDeployProvider
	
	constructor {
		$this.DBDeployProvider = $Args[0]
		$this.HandlerExecution = $Args[1]
	}
	
	method Add {
		$this.PatchMatches += $Args[0]
	}
	
	method Process {
		if ($this.DBDeployProvider.Checkpoint)
		{
			$this.DBDeployProvider.CheckpointPatchGroup($this.PatchMatches)
		}
		else
		{
			$this.DBDeployProvider.ExecutePatchGroup($this.HandlerExecution, $this.PatchMatches)
		}
	}
}

#endregion

#region $PatchSequencerClass
# -------------------------------------------------------------------------------------------
$PatchSequencerClass = New-PSClass PatchSequencer {
	note PatchMatchCollections @()
	note DBDeployProvider
	
	constructor {
		$this.DBDeployProvider = $Args[0]
	}
	
	method PatchMatchCollectionForHandlerExecution {
		$FileHandler = $Args[0]
		
		$HandlerExecution = $FileHandler.Execution

		# If no PatchMatchCollections or if it has a different HandlerExecution, make a new one
		if ( ($this.PatchMatchCollections.Count -eq 0) -or
		     (($this.PatchMatchCollections[ $this.Sequence.Count-1 ]).HandlerExecution -ne $HandlerExecution) )
		{
			$this.PatchMatchCollections += $PatchMatchCollectionClass.New($this.DBDeployProvider,$HandlerExecution)
		}
		
		# return last in list - it will be for the specified HandlerExecution
		$this.PatchMatchCollections[ $this.PatchMatchCollections.Count-1 ]
	}
	
	method Add {
		$FileHandler = $Args[0]
		$PatchMatch = $Args[1]
		$PatchMatchCollection = $this.PatchMatchCollectionForHandlerExecution($FileHandler)
		$PatchMatchCollection.Add($PatchMatch)
	}
	
	method Process {
		foreach ($PatchMatchCollection in $this.PatchMatchCollections)
		{
			$PatchMatchCollection.Process()
		}
	}
}

#endregion

#region $BaseDBDeployProviderClass
# -------------------------------------------------------------------------------------------
$BaseDBDeployProviderClass = New-PSClass DBDeployProvider {
	note -private DatabaseDir
	note -private DBFrameworkConfig
	note -private PatchGroupExecutors @{}
	note -private ScriptExecutors @{}

	note Checkpoint
	note Force

	note NonMatchingManifest @()
	note DatabaseConnected $FALSE
	
	constructor {
		$private.DatabaseDir = [System.IO.DirectoryInfo] $Args[0]
		Log-Warning "Database Dir $($private.DatabaseDir)"
		if (!$private.DatabaseDir.Exists)
		{
			Throw "Database Directory '$($private.DatabaseDir)' does not exist"
		}
		
		$private.ReadDBFrameworkConfig($Args[1])
		$this.Checkpoint = $FALSE
	}
	
	method ShouldPatchExecute {Throw "ShouldPatchExecute not implemented"}
	method MarkPatchAsExecuted {Throw "ExecutePatch not implemented"}
	method ExecuteBatch {Throw "ExecuteBatch not implemented"}
	
	method BeforeItemGroup{}
	
	method -private ReadDBFrameworkConfig {
		$ConfigFile = $Args[0]
		$config = ReadXmlFileIntoObjectTree $ConfigFile -NoExpansion
		$private.DBFrameworkConfig = $DBFrameworkConfigClass.AttachTo($config)
	}
	
	method AddPatchGroupExecutor {
		[string] $ExecutorName = $Args[0]
		[scriptblock] $ScriptHandler = $Args[1]
		$private.PatchGroupExecutors[$ExecutorName] = $ScriptHandler
	}
	
	method ExecutePatchGroup {
		[string] $HandlerExecution = $Args[0]
		$patchMatches = $Args[1]
		$ScriptHandler = $private.PatchGroupExecutors[$HandlerExecution] 
		if ($ScriptHandler -eq $null)
		{
			Throw "No Patch handler defined for type '$HandlerExecution'"
		}
		$ScriptHandler.InvokeReturnAsIs( @($patchMatches,$null) )
	}
	
	method CheckpointPatchGroup {
		$patchMatches = $Args[0]
		foreach ($patchMatch in $PatchMatches)
		{
			if ($WhatIf)
			{
				Log-Warning -Kind Message "WhatIf: Checkpoint $($patchMatch.manifest.Path)"
			}
			else
			{
				Log-Message -Kind Message "Checkpoint $($patchMatch.manifest.Path)"
				$this.MarkPatchAsExecuted($patchMatch.manifest.Path,$patchMatch.manifest.SourceCheckSum)
			}
		}
	}
	
	method AddScriptExecutor {
		[string] $ExecutorName = $Args[0]
		[scriptblock] $ScriptHandler = $Args[1]
		$private.ScriptExecutors[$ExecutorName] = $ScriptHandler
	}
	
	method ExecuteScript {
		[string] $ScriptType = $Args[0]
		$script = $Args[1]
		$ScriptHandler = $private.ScriptExecutors[$ScriptType] 
		if ($ScriptHandler -eq $null)
		{
			Throw "No Script handler defined for type '$ScriptType'"
		}
		$ScriptHandler.InvokeReturnAsIs( $script )
	}
	
	method  ProcessManifest {
		$Manifest = $Args[0]
		foreach ($manifestFile in $Manifest)
		{
			$PatchGroup = $private.DBFrameworkConfig.PatchGroupForManifest($manifestFile)

			
			if ($PatchGroup -ne $null)
			{
				$PatchGroup.PatchFiles += $manifestFile
			}
			else
			{
				$this.NonMatchingManifest += $manifestFile
			}
		}
	}
	
	method PatchMatchForManifest {
		$manifest = $Args[0]
		$FileHandler = $Args[1]
		$PatchGroup = $Args[2]
		return $PatchMatchClass.New($manifest, $FileHandler, $private.DatabaseDir, $PatchGroup)
	}
	
	method ProcessPatches {
		$Manifest = $Args
		
		$this.ProcessManifest($Manifest)

		foreach ($PatchGroup in $private.DBFrameworkConfig.PatchGroups.PatchGroup)
		{
			$PatchGroup.Process($this)
		}

		if ($this.NonMatchingManifest.count -gt 0)
		{
			Log-Warning -Kind Message "The Following Files did not match a patch pattern and were not applied"
			foreach($ManifestFile in $this.NonMatchingManifest)
			{
				Log-Warning -Kind Message "      $($ManifestFile.Path)"
			}
		}
	}
}

#endregion

#region function GetDbDeployProvider
# -------------------------------------------------------------------------------------------

function GetDbDeployProvider([string]$Providername)
{
	switch ($Providername)
	{
		'SQLServer'	
			{		
				$_SqlDeployProviderdir = Get-ScriptDir
				. "$($_SqlDeployProviderdir)\SqlServer\DBDeploySqlServerProvider.ps1"
				$DBDeploySqlServerProviderClass
			}
		default
			{
				Throw "Invalid DB Deployment Provider '$Providername'"
			}
	}
}
#endregion
